#region ;**** Directives created by AutoIt3Wrapper_GUI ****
#AutoIt3Wrapper_UseUpx=n
#AutoIt3Wrapper_Res_requestedExecutionLevel=asInvoker
#endregion ;**** Directives created by AutoIt3Wrapper_GUI ****
#include <SQLite.dll.au3>
#include <SqLite.au3>
#include <Crypt.au3>
#include <Array.au3>
#include <ButtonConstants.au3>
#include <EditConstants.au3>
#include <GUIConstantsEx.au3>
#include <TabConstants.au3>
#include <WindowsConstants.au3>
#include <Guiedit.au3>
#include <Guitab.au3>

; Constants
Global Const $version = 0.2
Global Const $serverip = "127.0.0.1"
Global Const $serverport = 33891
Global Const $iPendingcons = 5
Global Const $iMaxReceived = 10000
Global Const $sGameVersion = "0.1.0_1"

; Befehle
Global $avCommand[5][4]

$avCommand[0][0] = "kickall"
$avCommand[0][1] = 0
$avCommand[0][2] = "_Server_KickAll"
$avCommand[0][3] = "kickall"

$avCommand[1][0] = "kick"
$avCommand[1][1] = 1
$avCommand[1][2] = "_Client_Kick"
$avCommand[1][3] = "kick username/ip"

$avCommand[2][0] = "stop"
$avCommand[2][1] = 0
$avCommand[2][2] = "_EExit"
$avCommand[2][3] = "stop"

$avCommand[3][0] = "list"
$avCommand[3][1] = 1
$avCommand[3][2] = "_Server_List"
$avCommand[3][3] = "list online/all"

$avCommand[4][0] = "help"
$avCommand[4][1] = 0
$avCommand[4][2] = "_Server_CommandHelp"
$avCommand[4][3] = "help"

; Variablen
Global $hAccDatabase
Global $avConnections[1][5]
Global $hListenSocket

OnAutoItExitRegister("_Exit")


#region ### START Koda GUI section ### Form=
$hFormMain = GUICreate("WISH Server", 491, 337, 192, 124)
GUISetFont(8, 400, 0, "Verdana")
$Tab1 = GUICtrlCreateTab(8, 8, 473, 321)
$TabSheet1 = GUICtrlCreateTabItem("Console")
$Edit1 = GUICtrlCreateEdit("", 12, 32, 457, 265)
_GUICtrlEdit_SetReadOnly($Edit1, True)
$Input1 = GUICtrlCreateInput("", 12, 304, 385, 21)
$Button3 = GUICtrlCreateButton("Send", 404, 304, 67, 21)
$TabSheet2 = GUICtrlCreateTabItem("Server Control")
$Button1 = GUICtrlCreateButton("Shutdown Server", 12, 32, 123, 25)
$Button2 = GUICtrlCreateButton("Kick All Clients", 12, 64, 123, 25)
$Button4 = GUICtrlCreateButton("Create Account", 12, 96, 123, 25)
GUICtrlCreateTabItem("")
#endregion ### END Koda GUI section ###

#region ### START Koda GUI section ### Form=
$hFormCreateAcc = GUICreate("Account Creator", 233, 61, 302, 218, -1, BitOR($WS_EX_TOPMOST, $WS_EX_WINDOWEDGE))
GUISetFont(8, 400, 0, "Verdana")
$hCreateAccName = GUICtrlCreateInput("Name", 8, 8, 121, 21)
$hCreateAccPassword = GUICtrlCreateInput("Password", 8, 32, 121, 21)
$hCreateAccCreate = GUICtrlCreateButton("Create", 136, 16, 83, 25)
GUICtrlSetFont(-1, 12, 400, 0, "MS Sans Serif")
#endregion ### END Koda GUI section ###

GUISetState(@SW_SHOW, $hFormMain)

_ConsoleWrite("WISH Server v. " & $version)
_ConsoleWrite()
_ConsoleWrite("Startup:")

_ConsoleWrite("  - Initialize SQL Database", False, False)
If Not FileExists("databases") Then
	DirCreate("databases")
EndIf
$path = _SQLite_Startup(@ScriptDir & "\dlls\SQLite3.dll")
If $path = "" Then
	_ConsoleWrite("  ERROR", True, False)
	Exit
EndIf
_ConsoleWrite("  OK", True, False)

; Start Crypt Library
_ConsoleWrite("  - Start Up Crypt Library", False, False)
_Crypt_Startup()
If @error Then
	_ConsoleWrite("  Error (" & @error & ")", True, False)
	Exit
EndIf
_ConsoleWrite("  OK", True, False)

; Open/Connect SQL Database
_ConsoleWrite("  - Open SQL Database", False, False)
$hAccDatabase = _SQLite_Open(@ScriptDir & "\databases\account.db")
If @error Then
	_ConsoleWrite("  Error (" & @error & ")", True, False)
	Exit
EndIf
_ConsoleWrite("  OK", True, False)

; Create Listen Socket
_ConsoleWrite("  - Start Server", False, False)
TCPStartup()
$hListenSocket = TCPListen($serverip, $serverport, $iPendingcons)
If @error Then
	_ConsoleWrite("  Error (" & @error & ")", True, False)
	Exit
EndIf
_ConsoleWrite("  OK", True, False)
_ConsoleWrite("")

_ConsoleWrite("Server is running on " & $serverip & ":" & $serverport)

While 1
	_CheckNewClients()
	_ReceiveAll()
	$gMsg = GUIGetMsg(1)
	Switch $gMsg[0]
		Case -3
			Switch $gMsg[1]
				Case $hFormMain
					Exit
				Case $hFormCreateAcc
					GUISetState(@SW_HIDE, $hFormCreateAcc)
					GUICtrlSetData($hCreateAccName, "")
					GUICtrlSetData($hCreateAccPassword, "")
			EndSwitch
		Case $Button1
			Exit
		Case $Button2
			_Server_KickAll()
		Case $Button3
			_Server_Command(GUICtrlRead($Input1))
			GUICtrlSetData($Input1, "")
		Case $Button4
			GUISetState(@SW_SHOW, $hFormCreateAcc)
		Case $hCreateAccCreate
			$name = GUICtrlRead($hCreateAccName)
			$password = GUICtrlRead($hCreateAccPassword)
			$hashedpassword = _Crypt_HashData($password, $CALG_SHA1)
			$work = _SQL_CreateAccount($name, $hashedpassword)
			If $work Then
				_ConsoleWrite("Created Acc")
				_ConsoleWrite("  Name : " & $name, True, False)
				_ConsoleWrite("  Password : " & $password, True, False)
				_ConsoleWrite("  Hashed Password : " & $hashedpassword, True, False)
			Else
				_ConsoleWrite("Error while creating Acc (" & $work & ")")
			EndIf
			GUISetState(@SW_HIDE, $hFormCreateAcc)
			_GUICtrlTab_ActivateTab($Tab1, 0)
	EndSwitch
WEnd

Func _ConsoleWrite($text = "", $crlf = True, $stamp = True)
	If $text <> "" Then
		If $stamp Then
			_GUICtrlEdit_AppendText($Edit1, "[" & @HOUR & ":" & @MIN & "] ")
			; ConsoleWrite("[" & @HOUR & ":" & @MIN & "] ")
		Else
			_GUICtrlEdit_AppendText($Edit1, "          ")
		EndIf
		_GUICtrlEdit_AppendText($Edit1, $text)
		;ConsoleWrite($text)

		If $crlf Then
			_GUICtrlEdit_AppendText($Edit1, @CRLF)
			; ConsoleWrite(@CRLF)
		EndIf
	Else
		; ConsoleWrite(@CRLF)
		_GUICtrlEdit_AppendText($Edit1, @CRLF)
	EndIf
	_GUICtrlEdit_LineScroll($Edit1, 1, 0xfffffff)
EndFunc   ;==>_ConsoleWrite

Func _Server_CommandHelp()
	_ConsoleWrite("", True, False)
	_ConsoleWrite("Server Commands: ")
	For $i = 0 To UBound($avCommand) - 1 Step +1
		_ConsoleWrite("     -" & $avCommand[$i][3], True, False)
	Next
EndFunc   ;==>_Server_CommandHelp

Func _Server_List($sMode)
	_ConsoleWrite("List " & $sMode & " User")
	Switch $sMode
		Case "online"
			For $i = 0 To UBound($avConnections) - 1
				_ConsoleWrite("  -" & $avConnections[$i][3] & @TAB & $avConnections[$i][1], True, False)
			Next
		Case "all"
			$avUser = _SQL_UserList()
			For $i = 0 To UBound($avUser) - 1 Step +1
				_ConsoleWrite("  -" & $avUser[$i], True, False)
			Next
		Case Else
			Return "Parameter Wrong (""list online/all"")"
	EndSwitch
EndFunc   ;==>_Server_List

Func _SQL_UserList()
	Local $aResult, $aRow, $aTable
	$hList = _SQLite_GetTable($hAccDatabase, "SELECT Name FROM Accounts", $aResult, $aRow, $aTable)
	_ArrayDelete($aResult, 0)
	_ArrayDelete($aResult, 0)
	Return $aResult
EndFunc   ;==>_SQL_UserList

Func _Interpret($vRecv, $iID)
	$asplit = StringSplit($vRecv, "|")
	Switch $asplit[1]
		Case "login"
			_Client_LogIn($iID, $asplit[4], $asplit[5])
	EndSwitch
EndFunc   ;==>_Interpret

Func _EExit()
	Exit
EndFunc   ;==>_EExit

Func _Server_Command($sText)
	Local $avExecArray[1]
	$avExecArray[0] = "CallArgArray"
	If StringInStr($sText, " ") Then
		$split = StringSplit($sText, " ")
		$avSearch = _ArraySearch($avCommand, $split[1], 0, 0, 0, 0, 1, 0)
		If $avSearch <> -1 Then
			$iUbound = UBound($split)
			If $iUbound - 2 < $avCommand[$avSearch][1] Then
				_ConsoleWrite("Wrong Parameter: """ & $avCommand[$avSearch][3] & """")
			Else
				For $i = 0 To $avCommand[$avCommand][1]
					_ArrayAdd($avExecArray, $split[2 + $i])
				Next
				$return = Call($avCommand[$avSearch][2], $avExecArray)
				If $return <> "" Then _ConsoleWrite($return)
			EndIf
		Else
			_ConsoleWrite("Command Not Found")
		EndIf
	Else
		$avSearch = _ArraySearch($avCommand, $sText)
		If $avSearch <> -1 Then
			If 0 < $avCommand[$avSearch][1] Then
				_ConsoleWrite("Wrong Parameter: """ & $avCommand[$avSearch][3] & """")
			Else
				$return = Call($avCommand[$avSearch][2])
				If $return <> "" Then _ConsoleWrite($return)
			EndIf
		Else
			_ConsoleWrite("Command Not Found")
		EndIf
	EndIf

	GUICtrlSetData($Input1, "")
EndFunc   ;==>_Server_Command

Func _Client_Kick($sUsername)
	If $sUsername = "" Then
		Return "Kicked NOBODY"
	EndIf
	If _IsIp($sUsername) Then
		$iSearch = _ArraySearch($avConnections, $sUsername, 0, 0, 0, 0, 1, 1)
		If $iSearch = -1 Then
			Return "Couldn't find IP (" & $sUsername & ")"
		Else
			_RemoveClient($iSearch)
		EndIf
	Else
		$iSearch = _ArraySearch($avConnections, $sUsername, 0, 0, 0, 0, 1, 1)
		If $iSearch = -1 Then
			Return "Couldn't find Username (" & $sUsername & ")"
		Else
			_RemoveClient($iSearch)
		EndIf
	EndIf
	Return "Kicked " & $sUsername
EndFunc   ;==>_Client_Kick


Func _Server_KickAll()
	For $i = 0 To UBound($avConnections) - 1 Step +1
		If $avConnections[$i][0] <> "" Then
			_RemoveClient($i)
		EndIf
	Next
	Return "Kicked All Clients"
EndFunc   ;==>_Server_KickAll

Func _SQL_UserExist($username, $password)
	Local $aResult, $aRow, $aTable
	$exist = _SQLite_GetTable($hAccDatabase, "SELECT * FROM Accounts WHERE Name = """ & $username & """ AND Password = """ & $password & """", $aResult, $aRow, $aTable)
	If $exist = $SQLITE_OK Then
		If $aResult[0] > 4 Then
			_ConsoleWrite("Doubled Account : " & $username & "  " & $password)
		EndIf
		Return True
	Else
		Return False
	EndIf
EndFunc   ;==>_SQL_UserExist

Func _Client_Login($iID, $username, $password)
	If _SQL_UserExist($username, $password) Then
		; Richtig
		TCPSend($avConnections[$iID][0], "login|ok")
		$avConnections[$iID][3] = $username
	Else
		; Falsch
		TCPSend($avConnections[$iID][0], "login|wrong")
	EndIf
EndFunc   ;==>_Client_Login

Func _CheckNewClients()
	$hNewSocket = TCPAccept($hListenSocket)
	If $hNewSocket >= 0 Then
		_AddClient($hNewSocket)
	EndIf
EndFunc   ;==>_CheckNewClients

Func _AddClient($hSocket)
	Local $oldu = UBound($avConnections)
	If $avConnections[0][0] = "" Then
		$avConnections[0][0] = $hSocket
		$avConnections[0][1] = _SocketToIP($hSocket)
		$avConnections[0][2] = False
		$avConnections[0][3] = ""
	Else
		ReDim $avConnections[UBound($avConnections) + 1][5]
		$avConnections[$oldu][0] = $hSocket
		$avConnections[$oldu][1] = _SocketToIP($hSocket)
		$avConnections[$oldu][2] = False
		$avConnections[$oldu][3] = ""
	EndIf
	_ConsoleWrite("Add Socket: " & $hSocket)
EndFunc   ;==>_AddClient

Func _SQL_CreateAccount($username, $password)
	$error = _SQLite_Exec($hAccDatabase, "INSERT INTO Accounts VALUES (""" & $username & """,""" & $password & """)")
	If $error = $SQLITE_OK Then
		Return True
	Else
		Return @error
	EndIf
EndFunc   ;==>_SQL_CreateAccount

Func _RemoveClient($iClient)
	If $iClient = 0 Then
		TCPCloseSocket($avConnections[0][0])
		For $i = 0 To UBound($avConnections, 2) - 1
			$avConnections[0][$i] = ""
		Next
	Else
		TCPCloseSocket($avConnections[$iClient][0])
		_ArrayDelete($avConnections, $iClient)
	EndIf
EndFunc   ;==>_RemoveClient

Func _ReceiveAll()
	If $avConnections[0][0] = "" Then
		Return
	Else
		For $i = 0 To UBound($avConnections) - 1 Step +1
			$vRecv = TCPRecv($avConnections[$i][0], $iMaxReceived)
			If $vRecv <> "" Then
				_Interpret($vRecv, $i)
			ElseIf @error Then
				_RemoveClient($i)
			EndIf
		Next
	EndIf
EndFunc   ;==>_ReceiveAll

Func _Exit()
	_ConsoleWrite("", True, False)
	_ConsoleWrite("Shutdown:")

	_ConsoleWrite("  - Shutdown SQL Database", False, False)
	_SQLite_Close($hAccDatabase)
	_SQLite_Shutdown()
	_ConsoleWrite("  OK", True, False)


	_ConsoleWrite("  - Shutdown Crypt Library", False, False)
	_Crypt_Shutdown()
	_ConsoleWrite("  OK", True, False)

	_ConsoleWrite("  - Shutdown TCP Sockets", False, False)
	For $i = 0 To UBound($avConnections) - 1 Step +1
		If $avConnections[$i][0] <> "" Then
			TCPCloseSocket($avConnections[$i][0])
		EndIf
	Next
	TCPCloseSocket($hListenSocket)
	TCPShutdown()
	_ConsoleWrite("  OK", True, False)

	_ConsoleWrite("", True, False)
	_ConsoleWrite("Exit in 3 Seconds")
	Sleep(3000)
EndFunc   ;==>_Exit

Func _SocketToIP($SHOCKET)
	Local $sockaddr, $aRet

	$sockaddr = DllStructCreate("short;ushort;uint;char[8]")

	$aRet = DllCall("Ws2_32.dll", "int", "getpeername", "int", $SHOCKET, _
			"ptr", DllStructGetPtr($sockaddr), "int*", DllStructGetSize($sockaddr))
	If Not @error And $aRet[0] = 0 Then
		$aRet = DllCall("Ws2_32.dll", "str", "inet_ntoa", "int", DllStructGetData($sockaddr, 3))
		If Not @error Then $aRet = $aRet[0]
	Else
		$aRet = 0
	EndIf

	$sockaddr = 0

	Return $aRet
EndFunc   ;==>_SocketToIP

Func _IsIP($ip)
	Return StringRegExp($ip, "^(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9])\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[0-9])$")
EndFunc   ;==>_IsIP






